

#include "QMetaTypeEx.h"
#include "QArray.h"
#include "QDictionary.h"

#include <QtXml/QtXml>

const int QArray::Array = qMetaTypeId<QArray>();

static inline uint hash(float d)
{
#ifdef Q_CC_GNU
	// this is a GCC extension and isn't guaranteed to work in other compilers
	// the reinterpret_cast below generates a strict-aliasing warning with GCC
	union { float f; uint u; } cvt;
	cvt.f = d;
	return cvt.u;
#else
	return reinterpret_cast<uint&>(d);
#endif
}

uint qHash(const QVariant &variant)
{
	// simple and fast hash functions to differentiate between type and value
	int type = variant.userType();

	if (type == QVariant::String)
		return qHash(variant.toString());
	else if (type == QVariant::Double)
		return hash(variant.toDouble());
	else if (type == QVariant::Int)
		return 0x811890 + variant.toInt();
	else if (type == QVariant::Bool)
		return 0x371818 + variant.toBool();
	else if (type == QVariant::List)
		return 0x8377 + qvariant_cast<QVariantList>(variant).count();
	else if (type == QMetaType::Float)
		return hash(variant.toFloat());
	else if (type == QVariant::Map)
		return 0x01010101 + qvariant_cast<QVariantMap>(variant).count();
	else if (type == QVariant::Hash)
		return 0x02020202 + qvariant_cast<QVariantHash>(variant).count();
	else if (type == QMetaTypeEx::Dictionary)
		return 0x03030303 + qvariant_cast<QDictionary>(variant).count();
	else if (type == QMetaTypeEx::Array)
		return 0x04040404 + qvariant_cast<QArray>(variant).count();
	else if (type == QVariant::Invalid)
		return 0;

	return qHash(variant.typeName());
}


QArray::QArray()
{

}

QArray::QArray(const QArray &other)
	: QVariantList(other)
{

}

QArray::~QArray()
{

}

QArray &QArray::operator =(const QArray &other)
{
	QVariantList::operator=(other);
	return *this;
}

bool QArray::load(const QString &filename)
{
	QDomDocument doc("array");
	QFile file(filename);
	if (!file.open((QIODevice::ReadOnly)))
		return false;

	if (!doc.setContent(&file))
	{
		file.close();
		return false;
	}

	QDomElement docElem = doc.documentElement();

	QDomNode n = docElem.firstChild();

	while (!n.isNull())
	{
		QDomElement e = n.toElement();
		if (!e.isNull())
		{
			QString tag = e.tagName();
			QString value = e.text();

			if (tag == "array")
			{
				unserialize(e);
			}
		}

		n = n.nextSibling();
	}

	return true;
}

bool QArray::save(const QString &filename)
{
	QDomImplementation implementation;
	QDomDocumentType docType = implementation.createDocumentType(
		"plist", "-//Apple//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd");

	QDomDocument doc(docType);

	QDomProcessingInstruction instruction = doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
	doc.appendChild(instruction);

	QDomElement root = doc.createElement("plist");
	root.setAttribute("version", "1.0");
	doc.appendChild(root);

	QDomElement e = doc.createElement("array");
	serialize(doc, e);
	root.appendChild(e);

	QFile file(filename);

	if (!file.open(QIODevice::WriteOnly|QIODevice::Text))
		return false;

	QTextStream out(&file);
	out.setCodec("UTF-8");
	doc.save(out, 4);
	file.close();

	return true;
}

void QArray::display()
{
	qDebug()<<"<array>\n";

	const_iterator itr = begin();

	while (itr != end())
	{
		const QVariant &var = *itr;
		int type = var.userType();
		
		if (type == QMetaTypeEx::Dictionary)
		{
			QDictionary dict = var.value<QDictionary>();
			dict.display();
		}
		else if (type == QMetaTypeEx::Array)
		{
			QArray arr = var.value<QArray>();
			arr.display();
		}
		else
		{
			if (type == QMetaType::Int)
			{
				qDebug()<<"<integer>"<<var.toInt()<<"</integer>\n";
			}
			else if (type == QMetaType::Bool)
			{
				qDebug()<<"<bool>"<<var.toBool()<<"</bool>\n";
			}
			else if (type == QMetaType::QString)
			{
				qDebug()<<"<string>"<<var.toString()<<"</string>\n";
			}
			else if (type == QMetaType::Float)
			{
				qDebug()<<"<real>"<<var.toFloat()<<"</real>\n";
			}
		}

		++itr;
	}

	qDebug()<<"</array>\n";
}

bool QArray::serialize(QDomDocument &doc, QDomElement &root)
{
	const_iterator itr = begin();

	while (itr != end())
	{
		const QVariant &var = *itr;
		int type = var.userType();

        if (type == QMetaTypeEx::Dictionary)
		{
			QDomElement e = doc.createElement("dict");
			QDictionary dict = var.value<QDictionary>();
			dict.serialize(doc, e);
			root.appendChild(e);
		}
        else if (type == QMetaTypeEx::Array)
		{
			QDomElement e = doc.createElement("array");
			QArray arr = var.value<QArray>();
			arr.serialize(doc, e);
			root.appendChild(e);
		}
		else 
		{
			QDomElement e;
			QDomText t;

			if (type == QMetaType::Int)
			{
				int val = var.toInt();
				QString str;
				str.setNum(val);
				e = doc.createElement("integer");
				t = doc.createTextNode(str);
			}
			else if (type == QMetaType::Bool)
			{
				bool val = var.toBool();
				QString str;

				if (val)
				{
					str = "true";
				}
				else
				{
					str = "false";
				}

				e = doc.createElement("bool");
				t = doc.createTextNode(str);
			}
			else if (type == QMetaType::Float)
			{
				float val = var.toFloat();
				QString str;
				str.setNum(val);
				e = doc.createElement("real");
				t = doc.createTextNode(str);
			}
			else if (type == QMetaType::QString)
			{
				QString val = var.toString();
				e = doc.createElement("string");
				t = doc.createTextNode(val);
				e.appendChild(t);
				root.appendChild(e);
			}
			else
			{
				qDebug()<<"Unsupported type !!!\n";
				Q_ASSERT(0);
			}
		}

		++itr;
	}

	return true;
}

bool QArray::unserialize(QDomElement &docElem)
{
	QDomNode n = docElem.firstChild();

	while (!n.isNull())
	{		
		QDomElement e = n.toElement();

		if (!e.isNull())
		{
			QString tag = e.tagName();
			QString val = e.text();

			if (tag == "dict")
			{
				QDictionary dict;
				dict.unserialize(e);
				QVariant var;
				var.setValue(dict);
				push_back(var);
			}
			else if (tag == "array")
			{
				QArray arr;
				arr.unserialize(e);
				QVariant var;
				var.setValue(arr);
				push_back(var);
			}
			else
			{
				if (tag == "integer")
				{
					int v = val.toInt();
					QVariant var(v);
					push_back(var);
				}
				else if (tag == "real")
				{
					float v = val.toFloat();
					QVariant var(v);
					push_back(var);
				}
				else if (tag == "bool")
				{
					bool v;

					if (val == "true")
					{
						v = true;
					}
					else
					{
						v = false;
					}

					QVariant var(v);
					push_back(var);
				}
				else
				{
					QVariant var(val);
					push_back(var);
				}
			}
		}

		n = n.nextSibling();
	}

	return true;
}

